%{
    #include <cstdio>
    #include <iostream>
    #include "common.h"
    #include "enumdeclarations.h"

    using namespace std;

    extern int yylex();
    void yyerror(const char *s);

    //---------------------------------------------------------------------
    //---------------- variables used during parse process ----------------
    //---------------------------------------------------------------------
    /**
     * List of the variable indexes, which must be set after finding the end of the variable declarations.
     * @see RULES: variable_list, variable_declariations
     */
    vector<int> variablesToSetupIndexes;


    //---------------------------------------------------------------------
    //----------------- methods used during parse process -----------------
    //---------------------------------------------------------------------
    /**
     * Called when the program identifier is found.
     * Generates the startup jump
     */
    void onProgramIdentifier(int programIndex);

    /**
     * Called when the main begin statement is found.
     * Generates the startup label.
     */
    void onProgramBegin();
	
    /**
     * Called when the main end statement is found.
     * Generates the exit function.
     */
    void onProgramEnd();
    
    /**
     *	Called when the variable declaration found.
     *  @see RULE: variable_list
     */
    void onVariableDeclaration(int varIndex);

    /**
     *	Called when the variables declaration ends.
     *  @see RULE: variable_declariations
     */
    void onVariableDeclarationsEnd(VariableValueType variableValueType);

    /**
     *	Called when the number declaration is found.
     *  @see RULE: term:
     *                  TOKEN_NUBER_INT, TOKEN_NUBER_REAL
     */
    void onNumberDeclaration(int numIndex, SymbolTableElementType type);
    
    /**
     * Called when the assignment statement is found.
     * @see RULE: instruction: 
                        variable TOKEN_OP_ASSIGN term
     */
    void onAssignment(int lIndex, int rIndex);
    
    /**
     * Called when the arithmetic operation is found.
	 * Returns the index of the result variable
     * @see RULE: 
     *          expression
     */
    int onArithmeticOperation(int lIndex, int rIndex, ArithmeticOperationType opType);
	
	/**
     * Called when the unary minus is found.
     * @see RULE: 
     *          unary_expression
     */
	int onNegationOperation(int termIndex);
	
    void onWriteProc(int exprIndex);
	
%}
// -------------------------------------------
// ------------ TOKENS DEFINITION ------------
// -------------------------------------------
// RESERVED WORDS
%token TOKEN_PROGRAM
%token TOKEN_VAR
%token TOKEN_BEGIN
%token TOKEN_END

// OPERATORS
%token TOKEN_OP_ASSIGN
%token TOKEN_OP_REL
%token TOKEN_PLUS
%token TOKEN_MINUS
%token TOKEN_MULTIPLY
%token TOKEN_DIVIDE


// TYPES
%token TOKEN_TYPE_INT
%token TOKEN_TYPE_REAL

// EMBEDED FUNCTIONS/METHODS
%token TOKEN_READ
%token TOKEN_WRITE

// OTHERS
%token TOKEN_NUBER_INT
%token TOKEN_NUBER_REAL
%token TOKEN_IDENTIFIER

%%
//------------------------------------------- 
//----------- GRAMMAR DEFINITION ------------ 
//------------------------------------------- 
program: 
	TOKEN_PROGRAM program_identifier '(' program_variables_list ')' ';' variable_declariations main_instruction_block {
		cout << "BISON FOUND PROGRAM: " << $$ << endl; 
	}										
;

program_identifier: 
	TOKEN_IDENTIFIER {
		onProgramIdentifier($1);
	}
;

program_variables_list: 
	TOKEN_IDENTIFIER {
		symbolTable.setUpElementType($1, RECORD_TYPE_PROGRAM_VAR);
		cout << "BISON FOUND PROGRAM VAR IDENTIFIER: " << $1 << endl; 
	}      
	| program_variables_list ',' TOKEN_IDENTIFIER {
		symbolTable.setUpElementType($3, RECORD_TYPE_PROGRAM_VAR);
		cout << "BISON FOUND PROGRAM IDENTIFIER LIST: " << $3 << endl; 
	}
;

variable_declariations: 
	variable_declariations TOKEN_VAR variable_list ':' type ';' {
		onVariableDeclarationsEnd((VariableValueType) $5);
		cout << "BISON FOUND VAR DECLARATION:  " << $5 << endl; 
	}
	| {
		cout << "BISON INIT VAR DECLARATION" << endl; 
	}
;

variable_list: 
	TOKEN_IDENTIFIER {
		onVariableDeclaration($1);
		cout << "BISON FOUND IDENTIFIER: " << $1 << endl; 
	}      
	| variable_list ',' TOKEN_IDENTIFIER {
		onVariableDeclaration($3);
		cout << "BISON FOUND IDENTIFIER LIST: " << $3 << endl; 
	}
;

type: 
	TOKEN_TYPE_INT {
		$$ = VAR_VALUE_TYPE_INTEGER;
		cout << "BISON FOUND TYPE INT: " << $$ << endl; 
	} 
	| TOKEN_TYPE_REAL {
		$$ = VAR_VALUE_TYPE_REAL;
		cout << "BISON FOUND TYPE REAL" << endl; 
	} 
;

main_instruction_block:
	main_begin instruction_list main_end '.' {
		cout << "BISON FOUND MAIN INSTRUCTION BLOCK" << endl; 
	}
;

main_begin: 
	TOKEN_BEGIN {
		onProgramBegin();
	}
;

main_end: 
	TOKEN_END {
		onProgramEnd();
	}
;

instruction_list:
	instruction {
		cout << "BISON FOUND SINGLE INSTRUCTION" << endl; 
	}
	| instruction_list instruction {
		cout << "BISON FOUND INSTRUCTION LIST" << endl; 
	}
;
instruction: 
	variable TOKEN_OP_ASSIGN expression ';' {
		onAssignment($1, $3);
		cout << "BISON FOUND ASSIGN INSTRUCTION" << endl; 
	}
	| procedure
;

procedure: 
	TOKEN_WRITE '(' term ')' ';' {
		onWriteProc($3);
		cout << "BISON FOUND procedure" << endl;
	}
;	

expression: 
	expression TOKEN_PLUS div_mul_expression {
		cout << "BISON FOUND ADDITION" << endl; 
        int resultVarIndex = onArithmeticOperation($1, $3, ARITHMETIC_OP_ADDITION);
		$$ = resultVarIndex;
	}
	| expression TOKEN_MINUS div_mul_expression {
        cout << "BISON FOUND SUBTRACTION" << endl; 
		int resultVarIndex = onArithmeticOperation($1, $3, ARITHMETIC_OP_SUBTRACTION);
		$$ = resultVarIndex;
	}
	| div_mul_expression;
;

div_mul_expression: 
	div_mul_expression TOKEN_MULTIPLY unary_expression {
        cout << "BISON FOUND MULTIPLICATION" << endl; 
		int resultVarIndex = onArithmeticOperation($1, $3, ARITHMETIC_OP_MULTIPLICATION);
		$$ = resultVarIndex;
	}
	| div_mul_expression TOKEN_DIVIDE unary_expression {
	    cout << "BISON FOUND DIVISION" << endl; 
        int resultVarIndex = onArithmeticOperation($1, $3, ARITHMETIC_OP_DIVISION);
		$$ = resultVarIndex;
	}
	| unary_expression;
;
unary_expression: 
	'(' expression ')' {
		cout << "BISON FOUND PARENTHESIS EXPRESSION: " << $2 << endl;
		$$ = $2;
	}
	| TOKEN_MINUS term {
		cout << "BISON FOUND UNARY MINUS" << endl;
		int negationVarIndex = onNegationOperation($2);
		$$ = negationVarIndex;
	}
	| term {
		cout << "BISON FOUND TERM" << endl;
		$$ = $1; 
	}
;

term: 
	variable {
		cout << "BISON FOUND VARIABLE EXPR" << endl; 
		$$ = $1;
	}
	| TOKEN_NUBER_INT {
		onNumberDeclaration($1, RECORD_TYPE_INT_NUMBER);
		$$ = $1;
	    cout << "BISON FOUND INT EXPR" << endl; 
	}
	| TOKEN_NUBER_REAL {
		cout << "BISON FOUND REAL EXPR: " << $1 << endl;
		onNumberDeclaration($1, RECORD_TYPE_REAL_NUMBER);
		$$ = $1; 
	}
;

variable: 
	TOKEN_IDENTIFIER {
		cout << "BISON FOUND VARIABLE: " << endl; 
	}
;
%%
//------------------------------------------- 
//-------- FINAL METHOD DECLARATIONS -------- 
//------------------------------------------- 
void yyerror(const char *error) {
    cout << "PARSING ERROR OCCURED: " << error << endl;
    exit(-1);
}

void onProgramIdentifier(int programIndex) {
    symbolTable.setUpElementType(programIndex, RECORD_TYPE_PROGRAM);
    
    SymbolTableRecord& rec = symbolTable.getElement(programIndex);
    codegenerator.generateJump(rec.lexem);
}

void onProgramBegin() {
    SymbolTableRecord& rec = symbolTable.getProgramElement();
    codegenerator.generateLabel(rec.lexem);
}

void onProgramEnd() {
    codegenerator.generateExit();
}

void onVariableDeclaration(int varIndex) {
    variablesToSetupIndexes.push_back(varIndex);
    symbolTable.setUpElementType(varIndex, RECORD_TYPE_VARIABLE);
}

void onVariableDeclarationsEnd(VariableValueType variableValueType) {
    int varIndex;
    for (int i = 0; i < variablesToSetupIndexes.size(); i++) {
        varIndex = variablesToSetupIndexes[i];
        SymbolTableRecord& rec = symbolTable.getElement(varIndex);

        //set up the value type (integer or real) for the given variable
        rec.varValueType = variableValueType;
        //allocate address for the variable
        symbolTable.allocateAddress(varIndex);
    }

    //clear the indexes after setup
    variablesToSetupIndexes.clear();
}

void onNumberDeclaration(int numIndex, SymbolTableElementType type) {
    symbolTable.setUpElementType(numIndex, type);
}

void onAssignment(int lIndex, int rIndex) {
    codegenerator.generateAssignment(lIndex, rIndex);
}

int onArithmeticOperation(int lIndex, int rIndex, ArithmeticOperationType opType) {
    int resultVariableIndex = codegenerator.generateArithmeticOperation(lIndex, rIndex, opType);
	return resultVariableIndex; 
}

int onNegationOperation(int termIndex) {
	int negationVariableIndex = codegenerator.generateTermNegation(termIndex);
	return negationVariableIndex; 
}

void onWriteProc(int exprIndex) {
    codegenerator.generateWriteProc(exprIndex);
}